package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.service.IHotelService;
import cn.itcast.hotel.vo.PageResult;
import cn.itcast.hotel.vo.Params;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.*;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 酒店服务类，继承自ServiceImpl，实现了IHotelService接口。
 * 主要负责酒店数据的查询和操作。
 */
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Resource
    private HotelMapper hotelMapper;

    @Resource
    private RestHighLevelClient client;

    /**
     * 根据参数进行酒店搜索，返回分页结果。
     *
     * @param params 搜索参数，包含关键词、分页、排序等信息。
     * @return 搜索结果的分页对象，包含总条数和结果列表。
     */
    @Override
    public PageResult search(Params params) {
        try {
            SearchRequest searchRequest = new SearchRequest("hotel");
            BoolQueryBuilder boolQuery = boolQuery(params);

            // 根据排序方式设置排序
            if (!params.getSortBy().equals("default")) {
                searchRequest.source().sort(params.getSortBy(), SortOrder.ASC);
            }

            // 根据地理位置查询酒店
            if (StringUtils.isNotEmpty(params.getLocation())) {
                searchRequest.source().sort(SortBuilders.geoDistanceSort("location", new GeoPoint(params.getLocation()))
                        .order(SortOrder.ASC)
                        .unit(DistanceUnit.KILOMETERS)// km
                );
            }

            // 设置查询权重，提高广告酒店的评分
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(
                    boolQuery,
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                    QueryBuilders.termQuery("isAD", true),
                                    ScoreFunctionBuilders.weightFactorFunction(100)
                            )
                    }
            );

            // 添加分页参数
            int from = (params.getPage() - 1) * params.getSize(); // 计算from值，注意页码通常从1开始，所以需要减1
            searchRequest.source().from(from).size(params.getSize()); // 设置from和size
            searchRequest.source().query(functionScoreQueryBuilder);
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
            return search(response);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


    /**
     * 获取聚合数据
     * @return
     */
    @Override
    public Map<String, List<String>> filters(Params params) {
//        创建集合
        Map<String, List<String>> aggMap= null;
        try {
            SearchRequest request = aggregation(params);

            aggMap = new HashMap<>();
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            List<String> brandAgg = search(response, "brandAgg");
            List<String> cityAgg = search(response, "cityAgg");
            List<String> starNameAgg = search(response, "starNameAgg");
            aggMap.put("城市",cityAgg);
            aggMap.put("品牌",brandAgg);
            aggMap.put("星级",starNameAgg);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return aggMap;
    }

    @Override
    public List<String> suggestion(String key) {
        try {
            List<String> list = new ArrayList<>();
            SearchRequest request = new SearchRequest("hotel");
            request.source().suggest(
                    new SuggestBuilder().addSuggestion(
                            "MySuggestion",
                            SuggestBuilders.completionSuggestion("suggestion")
                                    .prefix(key)
                                    .skipDuplicates(true)
                                    .size(10)
                    )
            );
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            Suggest suggest = response.getSuggest();
            Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion = suggest.getSuggestion("MySuggestion");
            suggestion.forEach(entry -> {
                entry.getOptions().forEach(option -> {
                    String text = option.getText().string();
                    list.add(text);
                });
            });
            return list;
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
    }

    /**
     * 解析结果
     */
    private static  List<String> search(SearchResponse response,String AggName) {
        //        解析结果
        Aggregations aggregations = response.getAggregations();
        Terms brandAgg = aggregations.get(AggName);
        List<String> list =new ArrayList<>();
        brandAgg.getBuckets().forEach(bucket -> {
            String key = bucket.getKeyAsString();
            list.add(key);
        });
        return list;
    }

    /**
     * 封装聚合
     */
    private static SearchRequest aggregation(Params params) {
        SearchRequest request = new SearchRequest("hotel");
        BoolQueryBuilder boolQuery = boolQuery(params);
        request.source().query(boolQuery);
        request.source().size(0);
        request.source().aggregation(
                AggregationBuilders.terms("brandAgg")
                        .field("brand")
                        .size(50)
                        .order(BucketOrder.aggregation("_count", true))
        );
        request.source().aggregation(
                AggregationBuilders.terms("cityAgg")
                        .field("city")
                        .size(50)
                        .order(BucketOrder.aggregation("_count", true))
        );
        request.source().aggregation(
                AggregationBuilders.terms("starNameAgg")
                        .field("starName")
                        .size(50)
                        .order(BucketOrder.aggregation("_count", true))
        );
        return request;
    }
    /**
     * 构建BoolQuery查询条件。
     *
     * @param params 搜索参数对象。
     * @return BoolQueryBuilder 查询条件构建器。
     */
    private static BoolQueryBuilder boolQuery(Params params) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 根据参数添加各种条件查询
        if (StringUtils.isNotEmpty(params.getCity())) {
            boolQuery.must(QueryBuilders.termQuery("city", params.getCity()));
        }
        if (StringUtils.isNotEmpty(params.getBrand())) {
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        if (StringUtils.isNotEmpty(params.getStarName())) {
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
        if (StringUtils.isNotEmpty(params.getMinPrice()) && StringUtils.isNotEmpty(params.getMaxPrice())) {
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }
        if (!StringUtils.isEmpty(params.getKey())) {
            boolQuery.must(QueryBuilders.matchQuery("all", params.getKey()));
        } else {
            boolQuery.must(QueryBuilders.matchAllQuery());
        }
        return boolQuery;
    }

    /**
     * 处理搜索响应，解析结果并返回PageResult对象。
     *
     * @param response 搜索响应对象。
     * @return 包含搜索结果的PageResult对象。
     * @throws IOException 如果处理响应时发生IO异常。
     */
    private PageResult search(SearchResponse response) throws IOException {
        try {
            // 解析搜索结果
            SearchHits hits = response.getHits();
            long totalHits = hits.getTotalHits().value;

            // 如果没有命中结果，直接返回空结果
            if (totalHits == 0) {
                return new PageResult(0L, Collections.emptyList());
            }

            SearchHit[] searchHitsArray = hits.getHits();
            // 使用Java 8 Stream API转换搜索结果
            List<HotelDoc> hotelDocs = Arrays.stream(searchHitsArray)
                    .map(hit -> {
                        HotelDoc hotelDoc = JSON.parseObject(hit.getSourceAsString(), HotelDoc.class);
                        Object[] sortValues = hit.getSortValues();
                        if (sortValues.length > 0) {
                            hotelDoc.setDistance(sortValues[0]);
                        }
                        return hotelDoc;
                    })
                    .collect(Collectors.toList());

            return new PageResult(totalHits, hotelDocs);
        } catch (Exception e) {
            // 捕获并处理异常
            System.err.println("处理响应时发生异常: " + e.getMessage());
            throw new RuntimeException("处理响应时发生异常", e);
        }
    }

}
